using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using CppWrapper.GeneratorService;

namespace CppWrapper.GeneratorService
{
  internal class GeneratorServices
  {
    private readonly TypeDependencyHandler dependencyHandler;

    public GeneratorServices(TypeDependencyHandler dependencyHandler)
    {
      this.dependencyHandler = dependencyHandler;
    }

    internal string GetTemplateParameter(IEnumerable<Type> types)
    {
      StringBuilder parameter = new StringBuilder();
      foreach (Type type in types)
      {
        if (parameter.Length > 0)
        {
          parameter.Append("_");
        }
        bool isWrapped;
        parameter.Append(GetWrappedTypeName(type, true, true, out isWrapped));
      }
      return parameter.ToString();
    }

    public string GetWrappedTypeName(Type type, bool includeTemplateParameter, bool includeNamespace, out bool wrapped)
    {
      wrapped = false;
      if (type == null)
      {
        type = typeof(void);
      }

      if (type.IsGenericParameter)
      {
        return type.Name;
      }


      if (type.IsArray)
      {
        string wrappedElementTypeName = GetWrappedTypeName(type.GetElementType(), true, true, out wrapped);
        return string.Format("std::vector<{0}>", wrappedElementTypeName);
      }

      if (IsByRef(type) || type.IsPointer)
      {
        return GetWrappedTypeName(type.GetElementType(), includeTemplateParameter, includeNamespace, out wrapped);
      }

      string managedType = type.FullName;
      switch (managedType)
      {
        case "System.Int32":
          return "int";
        case "System.UInt32":
          return "unsigned int";
        case "System.Int64":
          return "__int64";
        case "System.UInt64":
          return "unsigned __int64";
        case "System.String":
          return "CString";
        case "System.Boolean":
          return "bool";
        case "System.Void":
          return "void";
        case "System.Single":
          return "float";
        case "System.Double":
          return "double";
        case "System.Byte":
          return "unsigned char";
        case "System.SByte":
          return "signed char";
        case "System.Int16":
          return "short";
        case "System.UInt16":
          return "unsigned short";
        case "System.Char":
          return "wchar_t";
        case "System.ValueType":
          return "System::CMObject";
        case "System.IntPtr":
          return "void*";
        default:
          {
            dependencyHandler.AddDependentType(type);
            wrapped = true;
            string simpleTypeName = type.Name;
            if (type.IsGenericType)
            {
              string templateParameter = string.Empty;
              if (includeTemplateParameter)
              {
                string genericTzpes = GetTemplateParameter(type.GetGenericArguments()).Replace("::", "_");
                templateParameter = string.Format("_{0}", genericTzpes);
              }
              if (includeNamespace)
              {
                return string.Format("{2}::CM{0}{1}", simpleTypeName.Split('`')[0], templateParameter, GetWrappedNamespace(type));
              }
              else
              {
                return string.Format("CM{0}{1}", simpleTypeName.Split('`')[0], templateParameter);
              }
            }
            if (includeNamespace)
            {
              return string.Format("{0}::CM{1}", GetWrappedNamespace(type), simpleTypeName);
            }
            else
            {
              return string.Format("CM{0}", simpleTypeName);
            }
          }
      }
    }

    private static string GetWrappedNamespace(Type type)
    {
      StringBuilder wrappedNamespaceBuilder = new StringBuilder();
      foreach (string namePart in type.Namespace.Split('.'))
      {
        wrappedNamespaceBuilder.Append("::");
        wrappedNamespaceBuilder.Append(namePart);
      }
      return wrappedNamespaceBuilder.ToString();

    }

    public string InsertNative2ManagedMarshalling(string content, Type managedType)
    {
      bool isWrapped;
      string sourceTypeName = GetWrappedTypeName(managedType, true, true, out isWrapped);
      string targetTypeName = GetCliTypeName(managedType, true);
      if (managedType.IsArray)
      {
        Type elementType = managedType.GetElementType();
        bool isElementTypeWrapped;
        string elementWrappedTypeName = GetWrappedTypeName(elementType, true, true, out isElementTypeWrapped);
        if (elementType == typeof(string))
        {
          return
            string.Format("wrapNative2ManagedStringArray({0})", content);

        }
        else if (!isElementTypeWrapped)
        {
          return
            string.Format("wrapNative2ManagedNotWrappedArray<{0}, {1}>({2})", GetCliTypeName(elementType, true),
                          elementWrappedTypeName, content);

        }
        else
        {
          return
            string.Format("wrapNative2ManagedClassArray<{0}, {1}>({2})", GetCliTypeName(elementType, true),
                          GetWrappedTypeName(elementType, true, true, out isWrapped), content);
        }
      }
      else if (IsEnum(managedType))
      {
        return string.Format("({0})({1})", targetTypeName, content);
      }
      else if (sourceTypeName == "CString" && targetTypeName == "::System::String^")
      {
        return string.Format("gcnew ::System::String({0})", content);
      }
      else if (managedType == typeof(IntPtr))
      {
        return string.Format("IntPtr((void*){0})", content);
      }
      else if (isWrapped)
      {
        return string.Format("({0})({1}).ThisObject()", targetTypeName, content);
      }
      return content;
    }

    public string InsertManaged2NativeMarshalling(string content, Type managedType)
    {
      bool isWrapped;
      string targetTypeName = GetWrappedTypeName(managedType, true, true, out isWrapped);
      if (IsEnum(managedType))
      {
        return string.Format("({1}::Enum)({0})", content, targetTypeName);
      }
      else if (managedType.IsArray)
      {
        Type elementType = managedType.GetElementType();
        return string.Format("wrapManaged2NativeClassArray<{0}, {1}>({2})", GetCliTypeName(elementType, true), GetWrappedTypeName(elementType, false, true, out isWrapped), content);
      }
      else if (managedType == typeof(IntPtr))
      {
        return string.Format("({0}).ToPointer()", content);
      }
      else if (isWrapped)
      {
        return string.Format("{0}({1})", targetTypeName, content);
      }
      return content;
    }

    public static bool IsEnum(Type type)
    {
      return type.BaseType != null && type.BaseType == typeof (Enum);
    }

    public string GetMethodParameter(bool withTypes, IEnumerable<ParameterInfo> parameters)
    {
      StringBuilder parameter = new StringBuilder();
      foreach (ParameterInfo parameterInfo in parameters)
      {
        if (parameter.Length > 0)
        {
          parameter.Append(" ,");
        }
        Type parameterType = parameterInfo.ParameterType;
        if (withTypes)
        {
          bool isWrapped;
          if (parameterType.IsPointer)
          {
            parameter.Append(GetWrappedTypeName(parameterType, true, true, out isWrapped));
            parameter.Append("*");
          }
          else if (IsEnum(parameterType))
          {
            parameter.Append(GetWrappedTypeName(parameterType, true, true, out isWrapped));
            parameter.Append("::Enum");

          }
          else
          {
            if (!parameterInfo.IsOut && !parameterInfo.ParameterType.IsByRef)
            {
              parameter.Append("const ");
            }
            parameter.Append(GetWrappedTypeName(parameterType, true, true, out isWrapped));
            if (parameterType.IsClass || isWrapped)
            {
              parameter.Append("&");
            }
          }
          parameter.Append(" ");
          parameter.Append(parameterInfo.Name);
        }
        else
        {
          parameter.Append(InsertNative2ManagedMarshalling(parameterInfo.Name, parameterType));
        }
      }
      return parameter.ToString();
    }

    public string GetCliTypeName(Type type, bool appendMarker)
    {
      type = type ?? typeof(object);
      string cliNamespace ;
      if (!type.IsNested)
        cliNamespace = type.Namespace.Replace(".", "::");
      else
        cliNamespace = type.DeclaringType.FullName.Replace(".", "::");
      bool isRef = type.IsByRef;
      string simpleTypeName = type.Name;
      if (isRef)
      {
        simpleTypeName = simpleTypeName.Replace("&", "");
      }
      string marker = "";
      if (appendMarker)
      {
        if (type.IsPointer)
        {
          marker = "*";          
        }
        else if (isRef && type.IsValueType)
        {
          marker = "%";
        }
        else if (isRef && !type.IsValueType)
        {
          marker = "^%";
        }
        else if (!type.IsValueType)
        {
          marker = "^";
        }
      }
      if (type.IsGenericType)
      {
        string name = simpleTypeName.Split('`')[0];
        StringBuilder genericArgumentBuilder = new StringBuilder();
        foreach (Type genericArgument in type.GetGenericArguments())
        {
          if (genericArgumentBuilder.Length > 0)
          {
            genericArgumentBuilder.Append(", ");
          }
          genericArgumentBuilder.Append(GetCliTypeName(genericArgument, true));
        }
        return string.Format("::{1}::{0}<{2}>{3}", name, cliNamespace, genericArgumentBuilder, marker);
      }
      else if (type.IsArray)
      {
        Type elementType = type.GetElementType();
        return string.Format("::cli::array<{0}>{1}", GetCliTypeName(elementType, true), marker);
      }
      else
      {
        return string.Format("::{1}::{0}{2}", simpleTypeName, cliNamespace, marker);
      }
    }

    public string GetHeaderFilename(Type type)
    {
      StringBuilder cppObjectName = GetCppObjectName(type);
      cppObjectName.Append(".h");
      return cppObjectName.ToString();
    }

    public string GetImplementationFilename(Type type)
    {
      StringBuilder cppObjectName = GetCppObjectName(type);
      cppObjectName.Append(".cpp");
      return cppObjectName.ToString();
    }

    private StringBuilder GetCppObjectName(Type type)
    {
      bool isWrapped;
      StringBuilder cppObjectName = new StringBuilder(GetWrappedTypeName(type, true, false, out isWrapped));
      cppObjectName.Replace("<", "_");
      cppObjectName.Replace(">", "_");
      cppObjectName.Replace(">", "'");
      return cppObjectName;
    }

    public static string GetWrapperStartNamespaceDeclaration(Type type)
    {
      StringBuilder namespaceDeclaration = new StringBuilder();
      foreach (string namePart in type.Namespace.Split('.'))
      {
        namespaceDeclaration.AppendFormat("namespace {0} {{ ", namePart);
      }
      return namespaceDeclaration.ToString();
    }

    public string GetIncludeStatements(Type type, bool asIncludes)
    {
      StringBuilder includeBuilder = new StringBuilder();
      IList<Type> includeTypes = new List<Type>();
      foreach (MemberInfo member in GetTypeMembers(type))
      {
        if (IgnoreMember(member))
        {
          continue;
        }
        if (member is MethodInfo)
        {
          MethodInfo methodInfo = (MethodInfo)member;
          Type nakedReturnType = GetNakedType(methodInfo.ReturnType);
          if (!includeTypes.Contains(nakedReturnType))
          {
            includeTypes.Add(nakedReturnType);
          }
          AddParameterTypes(includeTypes, methodInfo.GetParameters());
        }
        if (member is ConstructorInfo)
        {
          ConstructorInfo constructorInfo = (ConstructorInfo)member;
          AddParameterTypes(includeTypes, constructorInfo.GetParameters());
        }
      }
      AppendIncludeStatementForType(includeBuilder, includeTypes, asIncludes);
      return includeBuilder.ToString();
    }

    private static void AddParameterTypes(ICollection<Type> includeTypes, IEnumerable<ParameterInfo> parameters)
    {
      foreach (ParameterInfo parameterInfo in parameters)
      {
        Type nakedType = GetNakedType(parameterInfo.ParameterType);
        if (!includeTypes.Contains(nakedType))
        {
          includeTypes.Add(nakedType);
        }
      }
    }

    private void AppendIncludeStatementForType(StringBuilder includeBuilder, IList<Type> types, bool asIncludes)
    {
      for (int i = 0; i < types.Count; i++)
      {
        Type type = types[i];
        while (type.IsArray || type.IsByRef || type.IsPointer)
        {
          type = type.GetElementType();
        }
        bool wrapped;
        string typename = GetWrappedTypeName(type, true, false, out wrapped);
        if (wrapped)
        {
          if (IsEnum(type))
          {
            AppendIncludeStatement(type, includeBuilder);            
          }
          else if (asIncludes)
          {
            AppendIncludeStatement(type, includeBuilder);
          }
          else
          {
            AppendClassReference(type, includeBuilder, typename);
          }
        }
      }
    }

    private void AppendClassReference(Type type, StringBuilder includeBuilder, string typename)
    {
      includeBuilder.AppendLine(GetWrapperStartNamespaceDeclaration(type));
      includeBuilder.AppendLine(string.Format(@"class {0};", typename));
      includeBuilder.AppendLine(GetWrapperEndNamespaceDeclaration(type));
    }

    private void AppendIncludeStatement(Type type, StringBuilder includeBuilder)
    {
      includeBuilder.AppendLine(string.Format(@"#include ""{0}""", GetHeaderFilename(type)));
    }

    public string GetCliTemplateDefinition(Type type)
    {
      string templaceParameter = GetTemplateParameter(type.GetGenericArguments());
      if (templaceParameter.Length == 0)
      {
        return string.Empty;
      }
      else
      {
        return string.Format("template<{0}> ", templaceParameter);
      }
    }

    public string GetCliTemplateDefinition(MethodInfo info)
    {
      List<Type> typeList = new List<Type>();
      if (info.ReturnType != null && info.ReturnType.IsGenericParameter)
      {
        typeList.Add(info.ReturnType);
      }

      foreach (ParameterInfo parameterInfo in info.GetParameters())
      {
        if (parameterInfo.ParameterType.IsGenericParameter)
        {
          typeList.Add(parameterInfo.ParameterType);
        }
      }
      if (typeList.Count > 0)
      {
        return string.Format("template<{0}> ", GetTemplateParameter(typeList.ToArray()));
      }
      else
      {
        return string.Empty;
      }
    }

    public string GetWrapperEndNamespaceDeclaration(Type type)
    {
      StringBuilder namespaceDeclaration = new StringBuilder();
      foreach (string namePart in type.Namespace.Split('.'))
      {
        namespaceDeclaration.Append("} ");
      }
      return namespaceDeclaration.ToString();
    }

    public bool IgnoreMember(MemberInfo memberInfo)
    {
      if (memberInfo==null) { return true; }
      if (memberInfo.DeclaringType == typeof(object) || memberInfo.DeclaringType == typeof(ValueType))
      {
        return true;
      }
      if (IgnoreBecauseOfDefinedAttributes(memberInfo)) return true;
      if (memberInfo is MethodInfo)
      {
        MethodInfo methodInfo = (MethodInfo)memberInfo;
        if (methodInfo.Name=="Dispose")
        {
          return true;
        }
        if (methodInfo.GetBaseDefinition() != methodInfo)
        {
          return true;
        }
        if (methodInfo.IsGenericMethodDefinition)
        {
          return true;
        }
        if (IsParameterExcluded(methodInfo.ReturnParameter)) {return true;}
        foreach (ParameterInfo parameterInfo in methodInfo.GetParameters())
        {
          if (IsParameterExcluded(parameterInfo)) {return true;}
        }
      }
      if (memberInfo is FieldInfo)
      {
        FieldInfo fieldInfo = (FieldInfo) memberInfo;
        if (!fieldInfo.IsLiteral || !fieldInfo.IsStatic)
        {
          return true;
        }
      }
      if (memberInfo is PropertyInfo)
      {
        PropertyInfo propertyInfo = (PropertyInfo)memberInfo;
        MethodInfo getMethod = propertyInfo.GetGetMethod();
        MethodInfo setMethod = propertyInfo.GetSetMethod();
        bool invalidGetMethod = IgnoreMember(getMethod);
        bool invalidSetMethod = IgnoreMember(setMethod);
        if (invalidSetMethod && invalidGetMethod)
        {
          return true;
        }
      }
      return false;
    }

    public static bool IgnoreBecauseOfDefinedAttributes(MemberInfo memberInfo)
    {
      PropertyInfo propertyInfo = GetPropertyForGetSetMethod(memberInfo);
      if (propertyInfo!=null)
      {
        if (IgnoreBecauseOfDefinedAttributes(propertyInfo)) 
        {
          return true;
        }
      }
      IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(memberInfo);
      foreach (CustomAttributeData data in attributes)
      {
        if (IsObsoleteAttribute(data))
        {
          return true;
        }
        if (IsDoNotWrapAttribute(data))
        {
          return true;          
        }
      }
      return false;
    }

    private static PropertyInfo GetPropertyForGetSetMethod(MemberInfo memberInfo)
    {
      if (memberInfo is MethodInfo)
      {
        MethodInfo methodInfo = (MethodInfo) memberInfo;
        if (methodInfo.IsSpecialName)
        {
          if (methodInfo.Name.StartsWith("get_") || methodInfo.Name.StartsWith("set_"))
          {
            return memberInfo.DeclaringType.GetProperty(methodInfo.Name.Substring(4));
          }
        }
      }
      return null;
    }

    private static bool IsObsoleteAttribute(CustomAttributeData data)
    {
      return data.ToString().Contains("ObsoleteAttribute");
    }

    private static bool IsDoNotWrapAttribute(CustomAttributeData data)
    {
      return data.ToString().Contains("DoNotWrapAttribute");
    }

    private static bool IsParameterExcluded(ParameterInfo parameterInfo)
    {
      Type parameterType = GetNakedType(parameterInfo.ParameterType);
      if (IsTypeExcluded(parameterType))
      {
        return true;
      }
      if (parameterType.IsValueType && parameterInfo.IsOut)
      {
        return true;
      }
      if (IsArray(parameterInfo.ParameterType) && (parameterInfo.IsOut || IsByRef(parameterInfo.ParameterType)))
      {
        return true;
      }
      if (IsDelegate(parameterType))
      {
        return true;
      }
      if (IsArrayOfEnums(parameterInfo.ParameterType))
      {
        return true;
      }
      return false;
    }

    private static bool IsByRef(Type type)
    {
      if (type.IsByRef)
      {
        return true;
      }
      if (type.HasElementType && !type.IsArray)
      {
        return IsByRef(type.GetElementType());
      }
      return false;      
    }

    private static bool IsArray(Type type)
    {
      if (type.IsArray){
        return true;
      }
      if (type.HasElementType)
      {
        return IsArray(type.GetElementType());
      }
      return false;
    }

    private static Type GetNakedType(Type type)
    {
      return GetNakedType(type, false);
    }

    private static Type GetNakedType(Type type, bool noArrayElementType)
    {
      if (noArrayElementType && type.IsArray)
      {
        return type;
      }
      Type elementType = type.GetElementType();
      if (elementType==null)
      {
        return type;
      }
      return GetNakedType(elementType);
    }

    private static bool IsDelegate(Type type)
    {
      if (type == null || type == typeof(object))
      {
        return false;
      }
      if (GetNakedType(type) == typeof(Delegate))
      {
        return true;
      }
      return IsDelegate(type.BaseType);
    }

    private static readonly IList<string> excludedNamespaces = new string[]
      {
        "System.Reflection", 
        "System.Collections.Generic.IEnumerator",
        "System.Collections.IEnumerator"
      };

    private string wrappedType;

    public static bool IsTypeExcluded(Type type)
    {
      Type nakedType = GetNakedType(type);
      string typeNamespace = nakedType.FullName;
      foreach (string excludedNamespace in excludedNamespaces)
      {
        if (typeNamespace.StartsWith(excludedNamespace))
        {
          return true;
        }
      }
      return false;
    }

    private static bool IsArrayOfEnums(Type type)
    {
      Type nakedTypeArray = GetNakedType(type, true);
      if (nakedTypeArray.IsArray)
      {
        if (nakedTypeArray.GetElementType().IsArray)
        {
          return IsTypeExcluded(nakedTypeArray.GetElementType());
        }
        else if (nakedTypeArray.GetElementType().IsEnum)
        {
          return true;
        }
      }
      return false;
    }

    public static List<MemberInfo> GetTypeMembers(Type type)
    {
      List<MemberInfo> typeMembers = new List<MemberInfo>(type.GetMembers(BindingFlags.CreateInstance | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static));
      if (type.IsInterface)
      {
        GetAllInterfaces(typeMembers, type);
      }
      return typeMembers;
    }

    public static void GetAllInterfaces(List<MemberInfo> members, Type type)
    {
      foreach (Type baseInterface in type.GetInterfaces())
      {
        members.AddRange(baseInterface.GetMembers(BindingFlags.Public|BindingFlags.Static|BindingFlags.Instance));
        GetAllInterfaces(members, baseInterface);
      }
    }

    internal static string GetCppPropertyAccessorSeparator(string setDeclaration, string getDeclaration)
    {
      string getSetSeparator;
      if (!string.IsNullOrEmpty(setDeclaration) && !string.IsNullOrEmpty(getDeclaration))
      {
        getSetSeparator = ",";
      }
      else
      {
        getSetSeparator = string.Empty;
      }
      return getSetSeparator;
    }

    internal string GetCppPropertyAccessorDeclaration(MethodInfo method, string cppAccessor)
    {
      string accessorDeclaration;
      if (method!=null)
      {
        if (IgnoreMember(method))
        {
          return string.Empty;
        }
        accessorDeclaration = string.Format("{0}={1}", cppAccessor, method.Name);
      }
      else 
      {
        accessorDeclaration = string.Empty;
      }
      return accessorDeclaration;
    }

    internal void InsertCppPropertyDeclarationIntoHeaderFile(PropertyInfo propInfo, string setDeclaration, string getDeclaration, StringBuilder hFileContents, string getSetSeparator)
    {
      bool wrapped;
      wrappedType = GetWrappedTypeName(propInfo.PropertyType, true, true, out wrapped);
      if (propInfo.PropertyType.IsEnum)
      {
        wrappedType += "::Enum";
      }
      hFileContents.AppendFormat(@"
  __declspec( property( {0} {4} {1} ) ) {2} {3};
", getDeclaration, setDeclaration, wrappedType, propInfo.Name ,getSetSeparator);
    }
  }
}